use actix_web::dev::Extensions;
use actix_web::guard::{Guard, GuardContext};
use actix_web::http::header::ContentType;
use actix_web::http::{header, Method};
use actix_web::web::BufMut;
use actix_web::{
    get, guard, http, web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer, Result,
};
use serde::Deserialize;
use std::cell::RefMut;
use std::io;

async fn index() -> HttpResponse {
    HttpResponse::Ok().body("Helo")
}

// /users/show
#[get("/show")]
async fn show_users() -> HttpResponse {
    HttpResponse::Ok().body("Show users")
}

// /users/show/{id}
#[get("/show/{id}")]
async fn user_detail(path: web::Path<(u32,)>) -> HttpResponse {
    HttpResponse::Ok().body(format!("User detail: {}", path.into_inner().0))
}

#[get("/a/{v1}/{v2}")]
async fn req_path(req: HttpRequest) -> Result<String> {
    // 表示[匹配路径段的所有值在HttpRequest::match_info中都可以找到
    let v1: u8 = req.match_info().get("v1").unwrap().parse().unwrap();
    let v2: u8 = req.match_info().get("v2").unwrap().parse().unwrap();
    //
    let (v3, v4): (u8, u8) = req.match_info().load().unwrap();

    let v: Vec<u8> = req.match_info().load().unwrap();

    Ok(format!(
        "Values {} {} {} {} {} {}",
        v1, v2, v3, v4, v[0], v[1]
    ))
}

#[get("/index2/{username}/{id}/index.html")]
async fn index2(info: web::Path<(String, u32)>) -> Result<String> {
    let info = info.into_inner();
    Ok(format!("Welcome {}! id: {}", info.0, info.1))
}

#[derive(Deserialize)]
struct Info {
    username: String,
}

#[get("/info/{username}/index.html")]
async fn index3(info: web::Path<Info>) -> Result<String> {
    // 路径中的标识符必须和struct字段名一致
    let info = info.into_inner();
    Ok(format!("Welcome {}!", info.username))
}

#[get("/index4")]
async fn index4(req: HttpRequest) -> Result<HttpResponse> {
    // url_for的name似乎会优先找被注册了的函数名相同的url,然后才是通过name方法设置名称的url,这里优先查找了方法名为user_detail的函数,
    // 将其取消注册后才找到了/user/{name}.
    // 经过index5的验证这个想法是正确的
    let url = req.url_for("user_detail", ["御坂美琴"]).unwrap();

    println!("{:?}", url);

    Ok(HttpResponse::Found()
        .insert_header((header::LOCATION, url.as_str()))
        .finish())
}

#[get("/index5/{v1}")]
async fn index5(req: HttpRequest) -> Result<HttpResponse> {
    let v1: String = req.match_info().get("v1").unwrap().parse().unwrap();
    let url = req.url_for("req_path", [&v1, "3"]).unwrap();
    println!("url: {:?}", url);

    // 执行302跳转
    Ok(HttpResponse::Found()
        .insert_header((header::LOCATION, url.as_str()))
        .finish())
}

// 自定义路由守卫
struct ContentTypeHeader;

impl Guard for ContentTypeHeader {
    fn check(&self, ctx: &GuardContext<'_>) -> bool {
        let mut ref_mut = ctx.req_data_mut();
        ref_mut.insert(3u32);
        ref_mut.insert(String::from("abc"));
        ctx.head().headers.contains_key(header::CONTENT_TYPE)
    }
}

async fn cth(req: HttpRequest) -> String {
    let extensions = req.extensions();
    let option1: Option<&u32> = extensions.get::<u32>();
    println!("opt: {:?}", option1);

    let option2: Option<&String> = extensions.get::<String>();
    println!("opt: {:?}", option2);

    format!("{:?}:{:?}", option1, option2)
}

#[actix_web::main]
async fn main() -> io::Result<()> {
    HttpServer::new(|| {
        App::new()
            // 允许将多个路由注册到同一资源
            .route("/", web::get().to(index))
            .route("/user", web::post().to(index))
            .service(web::resource("/prefix").to(index))
            .service(
                web::resource("/user/{name}")
                    // 给资源一个名称,可以用于给现有资源继续添加路由
                    .name("user_detail")
                    .guard(guard::Header("content-type", "application/json"))
                    // 一个资源可以有多个守卫和多个route,一个route只能有一个处理程序
                    .route(web::get().to(|| async { HttpResponse::Ok().body("get") }))
                    .route(web::put().to(|| async { HttpResponse::Ok().body("put") })),
            )
            .service(
                web::resource("/path")
                    .route(
                        // GET /path HTTP/1.1
                        // Host: localhost:8080
                        // Content-Type: text/plain
                        // Content-Length: 7
                        //
                        // text...
                        web::route()
                            .guard(guard::Get())
                            .guard(guard::Header("content-type", "text/plain"))
                            .to(|| async { HttpResponse::Ok().body("get-path") }),
                    )
                    .route(
                        // POST /path HTTP/1.1
                        // Host: localhost:8080
                        // Content-Type: application/json
                        // Content-Length: 2
                        //
                        // {}
                        web::route()
                            // 注册一个新的守卫,每个路由可以注册任意数量的守卫
                            .guard(guard::Post())
                            .guard(guard::Header("content-type", "application/json"))
                            // 注册一个方法保护,一个路由可以注册任意数量的方法保护,这个方法保护其实就是http的请求方式(这里的post和guard中的post重合了)
                            .method(Method::POST)
                            // 为此路由注册一个异步处理函数,一个路由只能注册一个异步处理函数
                            .to(|| async { HttpResponse::Ok().body("post-path") }),
                    ),
            )
            .service(
                web::scope("/users")
                    // /users/show
                    .service(show_users)
                    // /users/show/{id}
                    .service(user_detail),
            )
            .service(req_path)
            .service(index2)
            .service(index3)
            .service(index4)
            .service(index5)
            .service(web::resource("/cth").route(web::route().guard(ContentTypeHeader).to(cth)))
            .service(
                web::resource("/no")
                    // 使用guard::Not可以反转守卫的结果.这里的效果是为所有非GET请求返回MethodNotAllowed
                    .guard(guard::Not(guard::Get()))
                    .to(HttpResponse::MethodNotAllowed)
            )
            .service(
                web::resource("/any")
                    // 使用guard::Any可以输入一个守卫列表,列表中任意一个匹配则视为匹配(or)
                    .guard(guard::Any(guard::Get()).or(guard::Post()))
                    .to(HttpResponse::MethodNotAllowed)
            )
            .service(
                web::resource("/all")
                    // 使用guard::All可以输入一个守卫列表,列表全部匹配则视为匹配(and)
                    .guard(guard::All(guard::Get()).and(guard::Post()))
                    .to(HttpResponse::MethodNotAllowed)
            )
            // 如何其他路由或资源都不匹配,则默认响应NOT FOUND,可以通过default_service来覆盖默认行为
            .default_service(
                web::route()
                    .guard(guard::Not(guard::Get()))
                    .to(HttpResponse::MethodNotAllowed)
            )
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

// 进度: https://actix.rs/docs/url-dispatch#external-resources
